iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0

今天就要來製作我們的第一個第三方庫啦!
目標是模仿[Day 21] 使用第三方庫--以json為例,製作出一個Config.cmake檔,讓find_package找到! 但並不包含任何有用資訊,單純作為讓使用者確定是否有安裝好依賴項。

語法

CMakePackageConfigHelpers

定義:使用configure_package_config_file前必須包含的助手函數。(固定寫法)

configure_package_config_file

定義:建立一個Config文件,讓使用者可以利用生成的文件來使用該庫。
原理和[Day 18] 版本號與宏使用 xxx.in文件生成其他文件很像,都是將 xxx.in中的某些元素替換成CMakeLists.txt中的變數來生成一個文件。

configure_package_config_file(<input> <output>
  INSTALL_DESTINATION <path>
  [PATH_VARS <var1> <var2> ... <varN>]
  )

input : 輸入文件,通常名為 xxxConfig.cmake.in

output : 輸出文件,通常名為 xxxConfig.cmake,find_package就是透過這個文件來找到第三方庫的。

INSTALL_DESTINATION : 安裝路徑,通常位於 usr/local/lib/cmake 中(在這裡也可以發現前兩天裝的 Opencv) 
或者是 usr/local/share/cmake 中(在這裡可以發現nlohmann_json),這些都是 find_package 預設會搜尋的路徑,在 Day 19有介紹過。

PATH_VARS <var1> <var2>... : 用CMake中的 var1、var2...變數替換xxxConfig.cmake.in文件中的 @var1@、@var2@...。

範例

  1. 編寫 MathFunctions / CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
# 根據自己的cmake版本來設定最小版本,要小於當前版本
project(MathFunctions VERSION 1.0.0 LANGUAGES CXX)
# 設定專案名稱為MathFunctions,版本為1.0.0,且此項目使用C++

add_library(mysqrt SHARED src/mysqrt.cpp)
# 將mysqrt.cpp編譯成庫
target_include_directories(mysqrt PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 設定庫的頭文件路徑
set_target_properties(mysqrt PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR})
# 設定庫的版本號,實做版本和專案版本相同,界面版本和主要版本相同

include(GNUInstallDirs)
# 引入 GNUInstallDirs 變數
install(FILES include/mysqrt.h DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) 
# 安裝庫到 /usr/local/include
install(TARGETS mysqrt DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) 
# 安裝頭文件到 /usr/local/bin

include(CMakePackageConfigHelpers)
configure_package_config_file(
	${PROJECT_SOURCE_DIR}/Config.cmake.in
	${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
	INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}
)
install(FILES ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake DESTINATION lib/cmake/${PROJECT_NAME})
# 讀入 ${PROJECT_SOURCE_DIR}/Config.cmake.in 文件
# 生成 ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 文件
# 並且將生成出的 Config.cmake 安裝到 /usr/local/lib/cmake/${PROJECT_NAME},讓find_package能夠在預設的搜索路徑找到
  1. 編寫 MathFunctions / Config.cmake.in
    以@PACKAGE_INIT@為開頭,以check_required_components(${PROJECT_NAME})當結尾是固定寫法,之後會擴充內容。
    只要有這個開頭和結尾,就可以生成 xxxConfig.cmake 檔囉!但是其中並不包含任何有用資訊。
@PACKAGE_INIT@

check_required_components(${PROJECT_NAME})
  1. 編譯並安裝

$ cd build
$ cmake ..
$ make
$ sudo make install

kai@esoc:~/2023_iT_CMake/Day22/MathFunctions/build$ sudo make install
[100%] Built target mysqrt
Install the project...
-- Install configuration: ""
-- Up-to-date: /usr/local/include/mysqrt.h
-- Up-to-date: /usr/local/lib/libmysqrt.so.1.0.0
-- Up-to-date: /usr/local/lib/libmysqrt.so.1
-- Up-to-date: /usr/local/lib/libmysqrt.so
-- Up-to-date: /usr/local/lib/cmake/MathFunctions/MathFunctionsConfig.cmake <----生成的Config.cmake檔⭐
  1. 編寫 Main / CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
# 根據自己的cmake版本來設定最小版本,要小於當前版本
project(cmake_totorial VERSION 1.0.0 LANGUAGES CXX)
# 設定專案名稱為cmake_totorial,版本為1.0.0,且此項目使用C++

find_package(MathFunctions REQUIRED)
# 尋找MathFunctionsConfig.cmake

add_executable(main src/main.cpp)
# 將main.cpp編譯成可執行文件main
target_link_libraries(main PUBLIC mysqrt)
# 將mysqrt連結到執行檔main

configure_file(version.h.in version.h)
# 將version.h.in輸出成version.h
target_include_directories(main PUBLIC "${PROJECT_BINARY_DIR}")
# 將PROJECT_BINARY_DIR加入main的include路徑,使main可以include到 build/version.h
  1. 編譯

$ cd build
$ make

  1. 執行

$ ./main 10

這個時候會發現跳出一個錯誤訊息

./main: error while loading shared libraries: libmysqrt.so.1: cannot open shared object file: No such file or directory

我們使用 ldd 指令來查看鍊結到的庫訊息,此時會發現鍊結器找不到該庫,
原因是鍊結器的路徑 並沒有包含 usr/local/lib。

ldd main

kai@esoc:~/2023_iT_CMake/Day22/Main/build$ ldd main 
	linux-vdso.so.1 (0x00007fff7efc4000)
	libmysqrt.so.1 => not found
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fcc13a00000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fcc13daf000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcc13600000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fcc13cc8000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fcc13dfb000)

所以現在將庫所在的路徑加入鍊結器的搜尋路徑中

$ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

接下來確認是否有正確加入

$ echo $LD_LIBRARY_PATH

kai@esoc:~/2023_iT_CMake/Day22/Main/build$ echo $LD_LIBRARY_PATH
/usr/local/lib::/usr/local/cuda/lib64

這個時候再執行就可以發現程式可以順利執行了。

$ ./main 10


是不是覺得這樣很麻煩?
不僅需要知道這個庫需要鍊結什麼庫(target_link_libraries(main PUBLIC mysqrt)),還要知道庫放在哪裡($LD_LIBRARY_PATH),因此明天會介紹如何像OpenCV 一樣,使用者只要像以下這樣包含定義好的頭文件路徑與庫路徑就好,不用了解具體的路徑就可以使用。

include_directories(${OpenCV_INCLUDE_DIRS})
# 將 OpenCV 的 include 路徑加入到專案中
add_executable(main src/main.cpp)
# 將 main.cpp 編譯成可執行文件 main
target_link_libraries(main ${OpenCV_LIBS})
# 將 OpenCV 的 library 路徑加入到專案中

P.S. 為什麼 nlohmann_json 並沒有提供find_package任何鍊結庫的資訊,卻不用像今天一樣要加入LD_LIBRARY_PATH才能執行呢?
因為 nlohmann_json 這個第三方庫其安裝的檔案都是 .hpp ,沒有像一般的第三方庫提供的是標頭檔與庫文件。


上一篇
[Day 21] 使用第三方庫--以json為例
下一篇
[Day 23] 製作第三方庫(二)
系列文
建構屬於自己的C/C++專案 : 我的30天CMake學習之旅29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言